// Random object system
//--------------------------------
#ifndef GENERATOR_H
#define GENERATOR_H
#include "common.h"
#include "random.h"
#include <stdexcept>

template< class T >
class Generator {
public:

    Generator();
    ~Generator();
    typedef Generator< T > GenType;

    template< class S >
    void AddType                       ( uint weight );
    void AddGenerator( const GenType* g, uint weight );
    T*   Generate() const;

private:

    class AbstractFactory {
    public:
        AbstractFactory() { }
        ~AbstractFactory() { }
        virtual T* Generate() const = 0;
    };
    template< class S >
    class Factory : public AbstractFactory {
    public:
        Factory() { }
        ~Factory() { }
        virtual T* Generate() const
        {
            return new S();
        }
    };
    typedef std::pair< AbstractFactory*, uint > TEntry;
    typedef std::pair< const GenType*, uint >   GEntry;
    typedef std::vector< TEntry >               TEntryList;
    typedef std::vector< GEntry >               GEntryList;
    TEntryList _tList;
    GEntryList _gList;

};

template< class T >
Generator< T >::Generator()
{
}

template< class T >
Generator< T >::~Generator()
{
    foreach ( TEntry& t, _tList ) {
        delete t.first;
    }
}

template< class T > template< class S >
void Generator< T >::AddType( uint weight )
{
    _tList.push_back( TEntry( new Factory< S >(), weight ) );
}

template< class T >
void Generator< T >::AddGenerator( const Generator< T >* g, uint weight )
{
    _gList.push_back( GEntry( g, weight ) );
}

template< class T >
T* Generator< T >::Generate() const
{
    int n = 0;
    foreach ( const TEntry& t, _tList ) {
        n += int( t.second );
    }
    foreach ( const GEntry& g, _gList ) {
        n += int( g.second );
    }

    if ( n == 0 )
        throw std::runtime_error( "Generating with empty generator" );
    int r = Random::Int( n );
    n = 0;
    foreach ( const TEntry& t, _tList ) {
        n += int( t.second );
        if ( r < n )
            return t.first->Generate();
    }
    foreach ( const GEntry& g, _gList ) {
        n += int( g.second );
        if ( r < n )
            return g.first->Generate();
    }
    return 0;
}

#endif
